﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SmartMathLibrary.Statistics.Distribution
{
    /// <summary>
    /// This class represents a Poisson distribution.
    /// </summary>
    [Serializable]
    public class PoissonDistribution
    {
        /// <summary>
        /// The lambda parameter of the distribution.
        /// </summary>
        private readonly double lambda;

        /// <summary>
        /// Initializes a new instance of the <see cref="PoissonDistribution"/> class.
        /// </summary>
        /// <param name="lambda">The lambda parameter of the distribution.</param>
        public PoissonDistribution(double lambda)
        {
            if (lambda <= 0)
            {
                throw new ArgumentException("lambda <= 0");
            }

            this.lambda = lambda;
        }

        /// <summary>
        /// Gets the lambda parameter of the distribution. 
        /// </summary>
        /// <value>The lambda parameter of the distribution.</value>
        public double Lambda
        {
            get { return this.lambda; }
        }

        /// <summary>
        /// Gets the mean of the distribution.
        /// </summary>
        /// <value>The mean of the distribution.</value>
        public double Mean
        {
            get { return this.lambda; }
        }

        /// <summary>
        /// Gets the median of the distribution.
        /// </summary>
        /// <value>The median of the distribution.</value>
        public double Median
        {
            get { return this.lambda + 1.0/3.0 - 0.02/this.lambda; }
        }

        /// <summary>
        /// Gets the mode of the distribution.
        /// </summary>
        /// <value>The mode of the distribution.</value>
        public double Mode
        {
            get
            {
                if (ExtendedMath.IsInteger(this.lambda))
                {
                    return this.lambda - 1;
                }

                return Math.Floor(this.lambda);
            }
        }

        /// <summary>
        /// Gets the variance of the distribution.
        /// </summary>
        /// <value>The variance of the distribution.</value>
        public double Variance
        {
            get { return this.lambda; }
        }

        /// <summary>
        /// Gets the skewness of the distribution.
        /// </summary>
        /// <value>The skewness of the distribution.</value>
        public double Skewness
        {
            get { return Math.Pow(this.lambda, -0.5); }
        }

        /// <summary>
        /// Computes the probability mass function of the Poisson distribution at the specified postion k.
        /// </summary>
        /// <param name="k">The specified position k.</param>
        /// <returns>The probability mass function of the Poisson distribution at the specified postion k.</returns>
        public double ProbabilityMassFunction(double k)
        {
            return (Math.Pow(Math.E, -this.lambda)*Math.Pow(this.lambda, k))/Factorials.Factorial((ulong) k);
        }

        /// <summary>
        /// Computes the cumulative distribution function of the Poisson distribution at the specified postion k.
        /// </summary>
        /// <param name="k">The specified position k.</param>
        /// <returns>The cumulative distribution function of the Poisson distribution at the specified postion k.</returns>
        public double CumulativeDistributionFunction(uint k)
        {
            double kClone = k;

            return (SpecialFunctions.IncompleteGammaFunction(Math.Floor(kClone + 1), this.lambda))/
                   (Factorials.Factorial((ulong) Math.Floor(kClone)));
        }
    }
}